import axios from "axios";
import type {
  AxiosInstance,
  Canceler,
  InternalAxiosRequestConfig,
} from "axios";
import type { HYRequestConfig } from "./type";

let accessToken = localStorage.getItem("access_token");
let refreshToken = localStorage.getItem("refresh_token");

class HYRequest {
  instance: AxiosInstance;
  cancelTokenMap: Map<string, Canceler>;
  isRefreshing: boolean;
  requests: Array<(token: string) => void>;
  constructor(config: HYRequestConfig) {
    this.cancelTokenMap = new Map();
    this.instance = axios.create(config);
    this.isRefreshing = false;
    this.requests = [];
    this.instance.interceptors.request.use(
      (config) => {
        console.log("全局请求成功的拦截");
        if (accessToken) {
          config.headers.Authorization = `Bearer ${accessToken}`;
        }
        return config;
      },
      (error) => {
        console.log("全局请求失败的拦截");
        return Promise.reject(error);
      }
    );
    this.instance.interceptors.response.use(
      (res) => {
        console.log("全局响应成功的拦截");
        return res.data;
      },
      async (error) => {
        console.log("全局响应失败的拦截");
        const { config, response } = error;
        if (response && response.status === 401) {
          if (!this.isRefreshing) {
            this.isRefreshing = true;
            try {
              const refreshResponse = await this.instance.post(
                "/refresh_token",
                {
                  refresh_token: refreshToken,
                }
              );
              accessToken = refreshResponse.data.access_token;
              refreshToken = refreshResponse.data.refresh_token;
              if (accessToken) {
                localStorage.setItem("access_token", accessToken);
              }
              if (refreshToken) {
                localStorage.setItem("refresh_token", refreshToken);
              }
              this.requests.forEach((callback) => {
                if (accessToken) {
                  callback(accessToken);
                }
              });
              this.requests = [];
              return this.instance(config);
            } catch (error) {
              return Promise.reject(error);
            } finally {
              this.isRefreshing = false;
            }
          } else {
            return new Promise((resolve) => {
              this.requests.push((token) => {
                config.headers.Authorization = `Bearer ${token}`;
                resolve(this.instance(config));
              });
            });
          }
        }
        return Promise.reject(error);
      }
    );
    this.instance.interceptors.request.use(
      config.interceptors?.requestSuccessFn,
      config.interceptors?.requestFailureFn
    );
    this.instance.interceptors.response.use(
      config.interceptors?.responseSuccessFn,
      config.interceptors?.responseFailureFn
    );
  }

  cancelRequest(requestTag: string) {
    const cancelFn = this.cancelTokenMap.get(requestTag);
    if (cancelFn) {
      cancelFn(`Request ${requestTag} has been canceled`);
      this.cancelTokenMap.delete(requestTag);
    }
  }

  cancelRequests(requestTags: string[]) {
    requestTags.forEach((tag) => this.cancelRequest(tag));
  }

  cancelAllRequests() {
    this.cancelTokenMap.forEach((cancelFn) => cancelFn());
    this.cancelTokenMap.clear();
  }

  request<T = any>(config: HYRequestConfig<T>) {
    const { retry = 3, retryDelay = 3000 } = config;
    let retries = 0;
    const makeRequest = () => {
      if (config.requestTag) {
        this.cancelRequest(config.requestTag);
        const source = axios.CancelToken.source();
        config.cancelToken = source.token;
        this.cancelTokenMap.set(config.requestTag, source.cancel);
      }
      if (config.interceptors?.requestSuccessFn) {
        config = config.interceptors.requestSuccessFn(
          config as InternalAxiosRequestConfig
        );
      }
      return new Promise<T>((resolve, reject) => {
        this.instance
          .request<any, T>(config)
          .then((res) => {
            if (config.interceptors?.responseSuccessFn) {
              res = config.interceptors.responseSuccessFn(res);
            }
            if (config.requestTag) {
              this.cancelTokenMap.delete(config.requestTag);
            }
            resolve(res);
          })
          .catch((error) => {
            if (config.requestTag) {
              this.cancelTokenMap.delete(config.requestTag);
            }
            if (retries < retry && config.method === "GET") {
              retries++;
              setTimeout(() => {
                makeRequest().then(resolve).catch(reject);
              }, retryDelay);
            } else {
              reject(error);
            }
          });
      });
    };
    return makeRequest();
  }

  get<T = any>(config: HYRequestConfig<T>) {
    return this.request({ ...config, method: "GET" });
  }

  post<T = any>(config: HYRequestConfig<T>) {
    return this.request({ ...config, method: "POST" });
  }

  delete<T = any>(config: HYRequestConfig<T>) {
    return this.request({ ...config, method: "DELETE" });
  }

  patch<T = any>(config: HYRequestConfig<T>) {
    return this.request({ ...config, method: "PATCH" });
  }
}

export default HYRequest;
